﻿using EnvDTE;
using System;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;

namespace Axiom.CodeManifold
{
	public static class SortExtension
	{
		public static void SortMembers_DEPRECATED(this Document doc)
		{
			if (doc == null || doc.ProjectItem == null || doc.ProjectItem.FileCodeModel == null)
			{
				throw new ArgumentNullException("No active code document available");
			}

			CodeElements elements = doc.ProjectItem.FileCodeModel.CodeElements;
			SortMembers(doc, elements);
		}

		public static void SortMembers(Document doc, CodeElements elements)
		{
			foreach (CodeElement element in elements)
			{
				switch (element.Kind)
				{
					case vsCMElement.vsCMElementNamespace:
					case vsCMElement.vsCMElementClass:
					case vsCMElement.vsCMElementStruct:
					case vsCMElement.vsCMElementInterface:
						SortMember(doc, element, element.Children);
						break;
					default:
						break;
				}
			}
		}

		private static string GetCode(Document doc, CodeElement element, CodeElement entity)
		{
			string result = string.Empty;

			int s = 0;
			int e = 0;
			Regex r = null;

			switch (doc.Language)
			{
				case "CSharp":
					s = element.GetStartPoint(vsCMPart.vsCMPartWholeWithAttributes).Line - 1;
					e = element.GetEndPoint(vsCMPart.vsCMPartWholeWithAttributes).Line;
					r = new Regex("^\\s*(//|/\\*|\\*/|\\*)");
					break;
				case "Basic":
					s = element.GetStartPoint(vsCMPart.vsCMPartWhole).Line - 1;
					e = element.GetEndPoint(vsCMPart.vsCMPartWhole).Line;
					r = new Regex("^\\s*'");
					break;
				default:
					throw new System.Exception("Invalid Language: " + doc.Language);
			}

			var sb = new StringBuilder(element.StartPoint.CreateEditPoint().GetLines(s + 1, e + 1));
			var line = entity.StartPoint.CreateEditPoint().GetLines(s, s + 1);

			while (line.TrimStart(new char[] { ' ', '\n', '\r' }).Length == 0 | r.Match(line).Success)
			{
				sb.Insert(0, line + Environment.NewLine);
				s -= 1;
				line = entity.StartPoint.CreateEditPoint().GetLines(s, s + 1);
			}

			result = sb.ToString();

			//string comment = string.Empty;
			//comment = (member is CodeFunction ? (member as CodeFunction).DocComment : string.Empty);

			return result;
		}

		private static void SortMember(Document doc, CodeElement container, CodeElements members)
		{
			var currentelementname = string.Empty;
			var a = new ArrayList();

			foreach (CodeElement member in members)
			{
				if (!member.IsCodeType)
				{
					a.Add(member);
				}
			}
			a.Sort(new MemberComparer());

			var sb = new StringBuilder(Environment.NewLine);
			var s = string.Empty;

			foreach (var member in a)
			{
				s = GetCode(doc, member as CodeElement, container);
				if (s.IndexOf(Environment.NewLine) > 0)
				{
					sb.Append(Environment.NewLine);
				}

				sb.Append(s);
				sb.Append(Environment.NewLine);
			}

			try
			{
				foreach (CodeElement member in a)
				{
					currentelementname = member.FullName;
					dynamic dynCodeElement = container;
					dynCodeElement.RemoveMember(member);
				}

				EditPoint ep = container.GetStartPoint(vsCMPart.vsCMPartNavigate).CreateEditPoint();
				ep.LineDown();
				ep.Insert(sb.ToString().TrimEnd(new char[] { '\n', '\r' }));
			}
			catch (NotImplementedException e)
			{
				throw new NotImplementedException(string.Format("The member '{0}' is multiple declared member, it can not be sorted.", currentelementname), e);
			}
			catch (Exception ex)
			{
				throw ex;
			}

			SortMembers(doc, members);
		}

		private class MemberComparer : System.Collections.IComparer
		{
			public int Compare(object x, object y)
			{
				CodeElement ex = x as CodeElement;
				CodeElement ey = y as CodeElement;
				int seqX = (int)GetSequence(ex);
				int seqY = (int)GetSequence(ey);

				if (seqX == seqY)
				{
					return ex.Name.CompareTo(ey.Name);
				}
				else
				{
					return (seqX > seqY ? 1 : -1);
				}

			}

			private object GetSequence(CodeElement member)
			{
				EnvDTE.vsCMElement kind = member.Kind;

				// Fields, Events, Enums first
				if (kind == vsCMElement.vsCMElementVariable | kind == vsCMElement.vsCMElementEvent | kind == vsCMElement.vsCMElementEnum)
				{
					return ((int)member.Kind * -10) - 10;
				}

				// Constructor, Destructor next
				if (kind == vsCMElement.vsCMElementFunction)
				{
					CodeFunction cf = member as CodeFunction;

					if (cf.FunctionKind == vsCMFunction.vsCMFunctionConstructor)
					{
						return -2;
					}

					if (cf.FunctionKind == vsCMFunction.vsCMFunctionDestructor)
					{
						return -1;
					}
				}

				// Property = Function
				if (kind == vsCMElement.vsCMElementProperty)
				{
					return 0;
				}

				// Move interface implementations to the end
				if (member.Name.IndexOf('.') > 0)
				{
					return (int)kind * 10 + 100000;
				}

				// Others
				return (int)kind * 10;

			}
		}
	}
}
